﻿/* HEADER
 * ------
 * © 2009 by Salomon Zwecker 
 * modified by:
 * - 
 */
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;

namespace Shapes.Geometry
{

    /// <summary>
    /// this Shape describes a filled curve using the quadratic B-Sline algorithm
    /// </summary>
    public class SplinePoly : Shape
    {
        internal Spline _Spline;
        /// <summary>
        /// The description of the curve
        /// </summary>
        public Spline Spline { get { return _Spline; } }

        readonly List<Vector2> _YCornerPoints = new List<Vector2>();

        ///<summary>
        /// Creates a new quadratic B-Spline Polygon
        ///</summary>
        ///<param name="spline">the description of the curve (note: some points may change it's position a little to avoid some errors) </param>
        public SplinePoly(Spline spline)
            : base()
        {
            _Spline = spline;
            _Spline.Close();

            _Transform = _Spline._Transform;

            _Spline.OnChangeGeometry += SplineOnChangeGeometry;

            CheckPointLocations();
        }

        void SplineOnChangeGeometry(Drawing obj)
        {
            CheckPointLocations();
        }

        private void CheckPointLocations()
        {
            List<SplinePoint> poly = _Spline._Points;

            for (int i = 0; i < poly.Count; i++)
            {
                int j = (i + 1) % poly.Count;
                int k = (j + 1) % poly.Count;

                if(!poly[j].IsHandler && 
                    (((poly[i].Position.Y < poly[j].Position.Y) && (poly[k].Position.Y < poly[j].Position.Y))
                    || ((poly[i].Position.Y > poly[j].Position.Y) && (poly[k].Position.Y > poly[j].Position.Y))))
                {
                    _YCornerPoints.Add(poly[j].Position);
                }
                if(poly[i].Position.Y == poly[j].Position.Y)
                {
                    poly[i] = new SplinePoint(poly[i].Position.X, poly[i].Position.Y + 0.0001f, poly[i].IsHandler);
                }
                //if (poly[i].Position.Y == 0 || poly[i].Position.Y == 1)
                //    poly[i] = new SplinePoint(poly[i].Position.X, poly[i].Position.Y + 0.0001f, poly[i].IsHandler);
            }
        }

        internal override bool IsPointInside(ref Vector2 point)
        {
            bool isInside = false;

            List<SplinePoint> poly = _Spline._Points;

            for (int i = 0; i < poly.Count; i++)
            {
                int j = (i + 1) % poly.Count;

                if (!poly[i].IsHandler && !poly[j].IsHandler)         // straight line
                {
                    if (    (poly[i].Position.Y < point.Y && poly[j].Position.Y >= point.Y)
                        ||  (poly[j].Position.Y < point.Y && poly[i].Position.Y >= point.Y))
                    {
                        if (poly[i].Position.X + (point.Y - poly[i].Position.Y)
                            / (poly[j].Position.Y - poly[i].Position.Y)
                            * (poly[j].Position.X - poly[i].Position.X) < point.X)
                        {
                            isInside = !isInside;
                        }
                    }
                }
                else if(poly[j].IsHandler)                          // spline curve
                {
                    Vector2 handlerPos = poly[j].Position;
                    int k = (j + 1) % poly.Count;

                    Vector2 f1 = (!poly[i].IsHandler)
                        ? poly[i].Position
                        : (poly[i].Position + handlerPos) / 2;

                    Vector2 f2 = (!poly[k].IsHandler)
                        ? poly[k].Position
                        : (handlerPos + poly[k].Position) / 2;

                    float bottomPart = 2 * (f1.Y + f2.Y - 2 * handlerPos.Y);
                    if (bottomPart == 0) // divide by zero prevention
                    {
                        // add a bit to the value
                       bottomPart += 0.0004f; // float.Epsilon is too small. It would cause rounding errors.

                    }

                    float sqrt = 2 * (handlerPos.Y - f1.Y);
                    sqrt *= sqrt;
                    sqrt -= 2 * bottomPart * (f1.Y - point.Y);

                    if (sqrt > 0)
                    {
                        sqrt = (float)Math.Sqrt(sqrt);

                        float topPart = 2 * (f1.Y - handlerPos.Y);

                        float fMinus = (topPart - sqrt) / bottomPart;
                        if (fMinus >= 0 && fMinus < 1)
                            CheckOutlineCrossed(fMinus, f1.X, f2.X, handlerPos.X, ref point, ref isInside);

                        float fPlus = (topPart + sqrt) / bottomPart;
                        if (fPlus > 0 && fPlus <= 1)
                            CheckOutlineCrossed(fPlus, f1.X, f2.X, handlerPos.X, ref point, ref isInside);
                    }
                }
            }

            return isInside;
        }

        private void CheckOutlineCrossed
            (float f, float f1X, float f2X, float handlerPosX, ref Vector2 point, ref bool isInside)
        {
            float tmp = f1X + f*(handlerPosX - f1X);
            float x = tmp + f*(handlerPosX + f*(f2X - handlerPosX) - tmp);

            if ((x < point.X) && !(_YCornerPoints.Contains(new Vector2(x, point.Y))))
                isInside = !isInside;
        }

        internal override float GetDistanceToEdge(ref Vector2 point)
        {
            return _Spline.GetDistanceToEdge(ref point);
        }

        internal override Vector2 GetNearestPointOnEdge(ref Vector2 point)
        {
            return _Spline.GetNearestPointOnEdge(ref point);
        }

        internal override Vector2 GetPositionFromT(float t)
        {
            return _Spline.GetPositionFromT(t);
        }

        internal override float GetEdgePathValueFromPoint(ref Vector2 point)
        {
            return _Spline.GetEdgePathValueFromPoint(ref point);
        }

        internal override Vector2 GetTangent(ref Vector2 position)
        {
            return _Spline.GetTangent(ref position);
        }

        internal override Vector2 GetNormal(ref Vector2 position)
        {
            return _Spline.GetNormal(ref position);
        }

        /// <summary>
        /// Clones the Spline
        /// </summary>
        /// <returns>
        /// a clone of the type QuadBSplinePoly
        /// </returns>
        public override object Clone()
        {
            SplinePoly obj = new SplinePoly((Spline)_Spline.Clone())
                                      {
                                          _Transform = (Transformation2D) _Transform.Clone()
                                      };
            return obj;
        }

        /// <summary>
        /// Releases all references inside of the class.
        /// </summary>
        public override void Dispose()
        {
            _Spline.Dispose();
            _Spline = null;
            base.Dispose();
        }

        /// <summary>
        /// Gets the enumeration type which is mapped to this kind of object
        /// </summary>
        /// <returns>the geometry typee of this object</returns>
        public override GeometryType GetGeometryType()
        {
            return GeometryType.SplinePoly;
        }
    }
}
